package Groep1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * De parser herkent zinnen van een simpele grammatica.
 * Deze grammatica is G(&lt;expression&gt;)<br><br>
 * &nbsp;&lt;expression&gt; ::= &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;expression-tail&gt; ::= e | &lt;plus-expression&gt; | &lt;minus-term&gt;<br>
 * &nbsp;&lt;plus-expression&gt; ::= PLUS &lt;expression&gt;<br>
 * &nbsp;&lt;minus-term&gt; ::= MINUS &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;term&gt; ::= &lt;factor&gt; &lt;term-tail&gt;<br>
 * &nbsp;&lt;term-tail&gt; ::= e | &lt;times-term&gt;<br>
 * &nbsp;&lt;times-term&gt; ::= TIMES &lt;term&gt;<br>
 * &nbsp;&lt;factor&gt; ::= PARENT_LEFT &lt;expression&gt; PARENT_RIGHT<br>
 * &nbsp;&lt;factor&gt; ::= ID | INT | LONG | FLOAT | DOUBLE<br>
 * [Namen in hoofdletters zijn tokens die door de scanner herkend worden.]<br>
 * Het resultaat van parsen is een string representatie van de oorspronkelijke
 * expressie met daaraan haakjes toegevoegd die de volgorde van evaluatie
 * duidelijk maakt. Bijvoorbeeld, de invoer expressie <code>1+2*3</code>
 * resulteert in <code>(1+(2*3))</code>, omdat de vermenigvuldig operator
 * sterker bindt (en dus eerder wordt ge&euml;valueerd) dan de optel operator.
 */
class Parser implements TokenKinds {

    /**
     * De scanner die de tokens levert voor deze parser.
     */
    private Scanner scanner;

    /**
     * 1 lookahead voor een LL(1) parser.
     */
    private Token lookahead;
    private Map<String,NonTerminal> nonterminals;
    private Map<String,Terminal> terminals;
    private boolean firstnt=true;

    /**
     * Een fout is opgetreden bij het parsen van de nonterminal.
     * @param nonterminal naam van nonterminal
     * @param message foutmelding
     */
    private void parseError(String nonterminal,String message) {
        System.err.println();
        System.err.println("parse error in <"+nonterminal+"> : "+message);
        System.err.print("lookahead '"+lookahead.getText()+"' ");
        System.err.print("(kind="+lookahead.getKind()+") ");
        System.err.print("at "+lookahead.getLine()+"/"+lookahead.getColumn());
        System.err.println();
        throw new RuntimeException();
    }

    /**
     * Accepteer huidige token van scanner.
     */
    private void acceptIt() {
        try {
            lookahead=scanner.nextToken();
        } catch (IOException exception) {
            exception.printStackTrace();
            throw new RuntimeException();
        }
    }

    /**
     * Accepteer huidige token van scanner, mits deze van een bepaald soort is.
     * @param nonterminal naam van nonterminal
     * @param kind identifatie van token soort
     */
    private void accept(String nonterminal,int kind) {
        if ( lookahead.getKind()!=kind ) {
            parseError(nonterminal,"kind="+kind+" expected");
        } else {
            acceptIt();
        }

    }

    /**
     * Construeer een nieuwe parser op de input stream.
     * @param in input stream van characters
     */
    public Parser(InputStream in) {
        scanner=new Scanner(new BufferedReader(new InputStreamReader(in)));
        nonterminals=new LinkedHashMap<String,NonTerminal>();
        terminals=new HashMap<String,Terminal>();
        //Initialiseer lookahead met eerste token van scanner
        acceptIt();
    }

    /**
     * Parse het root symbool van de grammatica.
     * @return het resultaat van parsing (in dit geval een string)
     */
    public Map<String,NonTerminal> parseRoot() {
        //System.out.println("ParseRoot");
        firstnt=true;
        while(lookahead.getKind()!=EOF) {
            parseProductionLine();
        }
        //Na parsen van root symbol moet EOF token gescanned worden
        accept("root",EOF);
        //Geef de root van de opgebouwde expressie stack terug
        return nonterminals;
    }

    private void parseProductionLine() {
        //System.out.println("ParseProductionLine");
        NonTerminal nt = parseNonTerminal();
        // Followers Rule 1
        if(firstnt) {
            nt.addFollower(new Terminal("$"));
            firstnt=false;
        }
        accept("productionLine",ASSIGNMENT);
        nt.addProductionRule(parseProductionRule());
        while(lookahead.getKind()==OR) {
             accept("productionLine",OR);
             nt.addProductionRule(parseProductionRule());
        }
        accept("productionLine",EOL);
    }

    private LinkedList<Element> parseProductionRule() {
        //System.out.println("ParseProductionRule");
        LinkedList<Element> retval=new LinkedList<Element>();
        if(lookahead.getKind()!=NONTERMINAL&&lookahead.getKind()!=TERMINAL)
		        parseError("productionrule","nonterminal or terminal expected");
        while(lookahead.getKind()==NONTERMINAL ||
                lookahead.getKind()==TERMINAL) {
            if(lookahead.getKind()==TERMINAL)
                retval.add(parseTerminal());
            else
                retval.add(parseNonTerminal());
        }
        return retval;
    }

    private Terminal parseTerminal() {
        Terminal term;
        //System.out.println("ParseTerminal");
        term= new Terminal(lookahead.getText());
        accept("Terminal",TERMINAL);
        return term;
    }

    private NonTerminal parseNonTerminal() {
        NonTerminal nt;
        //System.out.println("ParseNonTerminal");
        if(nonterminals.containsKey(lookahead.getText()))
            nt=nonterminals.get(lookahead.getText());
        else {
            nt= new NonTerminal(lookahead.getText());
            nonterminals.put(nt.getName(),nt);
        }
        accept("NonTerminal",NONTERMINAL);
        return nt;
    }



    /**
     * Een interne stack om string representatie van expressie te kunnen bouwen.
     */
    private LinkedList expressionStack=new LinkedList();

    /**
     * Push (tekst van) current token op de interne stack.
     */
    private void pushIt() {
        push(lookahead.getText());
    }

    /**
     * Push een object op de interne stack.
     * @param object object dat bovenop de stapel wordt geplaatst
     */
    private void push(Object object) {
        expressionStack.addFirst(object);
    }

    /**
     * Haal bovenste object van de interne stack.
     * @return bovenste verwijderde element
     */
    private Object pop() {
        return expressionStack.removeFirst();
    }

}
